本节代码对应 GitHub 分支: chapter7

仓库传送门 (opens new window)

# axios 请求准备

//api/request.js
export const getSingerInfoRequest = id => {
  return axiosInstance.get (`/artists?id=${id}`);
};

# redux 层开发

# 1. 声明初始化 state

//store/reducer.js
import * as actionTypes from './constants';
import { fromJS } from 'immutable';

const defaultState = fromJS ({
  artist: {},
  songsOfArtist: [],
  loading: true
});

# 2. 定义 constants

//store/constants.js
export const CHANGE_ARTIST = 'singer/CHANGE_ARTIST';
export const CHANGE_SONGS_OF_ARTIST = 'singer/CHANGE_SONGS_OF_ARTIST';
export const CHANGE_ENTER_LOADING = 'singer/CHNAGE_ENTER_LOADING';

# 3. 定义 reducer 函数

//store/reducer.js
export default (state = defaultState, action) => {
  switch (action.type) {
    case actionTypes.CHANGE_ARTIST:
      return state.set ('artist', action.data);
    case actionTypes.CHANGE_SONGS_OF_ARTIST:
      return state.set ('songsOfArtist', action.data);
    case actionTypes.CHANGE_ENTER_LOADING:
      return state.set ('loading', action.data);
    default:
      return state;
  }
}

# 4. 编写具体的 action

import { CHANGE_SONGS_OF_ARTIST, CHANGE_ARTIST, CHANGE_ENTER_LOADING } from './constants';
import { fromJS } from 'immutable';
import { getSingerInfoRequest } from './../../../api/request';

const changeArtist = (data) => ({
  type: CHANGE_ARTIST,
  data: fromJS (data)
});

const changeSongs = (data) => ({
  type: CHANGE_SONGS_OF_ARTIST,
  data: fromJS (data)
})
export const changeEnterLoading = (data) => ({
  type: CHANGE_ENTER_LOADING,
  data
})

export const getSingerInfo = (id) => {
  return dispatch => {
    getSingerInfoRequest (id).then (data => {
      dispatch (changeArtist (data.artist));
      dispatch (changeSongs (data.hotSongs));
      dispatch (changeEnterLoading (false));
    })
  }
}

# 5. 将相关变量导出

//index.js
import reducer from './reducer'
import * as actionCreators from './actionCreators'

export { reducer, actionCreators };

# 组件连接 Redux

首先,需要将 Singer 下的 reducer 注册到全局 store,在 src 目录下的 store/reducer.js 中,内容如下:

import { combineReducers } from 'redux-immutable';
import { reducer as recommendReducer } from '../application/Recommend/store/index';
import { reducer as singersReducer } from '../application/Singers/store/index';
import { reducer as rankReducer } from '../application/Rank/store/index';
import { reducer as albumReduimport { reducer as singerInfoReducer } from "../application/Singer/store/index";
import { reducer as singerInfoReducer } from "../application/Singer/store/index";

export default combineReducers ({
  recommend: recommendReducer,
  singers: singersReducer ,
  rank: rankReducer,
  album: albumReducer,
  singerInfo: singerInfoReducer
});

现在在 Singer/index.js 中,准备连接 Redux。增加代码:

import { connect } from 'react-redux';
import { getSingerInfo, changeEnterLoading } from "./store/actionCreators";

// 组件代码省略

// 映射 Redux 全局的 state 到组件的 props 上
const mapStateToProps = state => ({
  artist: state.getIn (["singerInfo", "artist"]),
  songs: state.getIn (["singerInfo", "songsOfArtist"]),
  loading: state.getIn (["singerInfo", "loading"]),
});
// 映射 dispatch 到 props 上
const mapDispatchToProps = dispatch => {
  return {
    getSingerDataDispatch (id) {
      dispatch (changeEnterLoading (true));
      dispatch (getSingerInfo (id));
    }
  };
};

// 将 ui 组件包装成容器组件
export default connect (mapStateToProps,mapDispatchToProps)(React.memo (Singer));

同时组件代码做如下添加:

// 记得删除 mock 数据

const {
  artist: immutableArtist,
  songs: immutableSongs,
  loading,
} = props;

const { getSingerDataDispatch } = props;

const artist = immutableArtist.toJS ();
const songs = immutableSongs.toJS ();

同时 SongList 组件中传入的 songs 参数应该改为现在从 redux 取得的 songs 变量。

# 组件对接真实数据

很简单,我们在 useEffect 中添加请求代码即可:

useEffect (() => {
  const id = props.match.params.id;
  getSingerDataDispatch (id);
  // 之前写的 UI 处理逻辑省略
}, []);

# 添加 loading

import Loading from "./../../baseUI/loading/index";

return (
  //Container 组件下面
  { loading ? (<Loading></Loading>) : null}
)

至此,一个精美的歌手详情页就打造完成了。恭喜你,又解锁了一个新的模块。

下一章,我们开始本项目最复杂、最具有挑战性的模块:播放器模块开发。大家加油!

阅读全文